/*
 * Created on Oct 27, 2006
 * 
 * $Id: Polygon.java Feb 8, 2004 izpack-frontend
 * Copyright (C) 2005 Andy Gombos
 * 
 * File : Polygon.java 
 * Description : TODO Add description
 * Author's email : gumbo@users.berlios.de
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package slicegen;

import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Stack;

public class Polygon implements Shape
{
	public Polygon()
	{
		path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
	}

	public Polygon(Shape s)
	{
		path = new GeneralPath(s);
		path.setWindingRule(GeneralPath.WIND_EVEN_ODD);
	}

	public Polygon(double points[][])
	{
		path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);

		path.moveTo((float) points[0][0], (float) points[0][1]);

		for (int i = 1; i < points.length; i++)
		{
			path.lineTo((float) points[i][0], (float) points[i][1]);
		}

		path.closePath();
	}

	public Polygon(ArrayList<Line> lines)
	{
		path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);

		for (Line line : lines)
		{
			path.append(line, false);
		}
	}

	public void lineTo(double x, double y)
	{
		path.lineTo((float) x, (float) y);
	}

	public void lineTo(Point2D pt)
	{
		path.lineTo((float) pt.getX(), (float) pt.getY());
	}

	public void moveTo(double x, double y)
	{
		path.moveTo((float) x, (float) y);
	}

	public void moveTo(Point2D pt)
	{
		path.moveTo((float) pt.getX(), (float) pt.getY());
	}

	public void close()
	{
		path.closePath();
	}

	public void add(Shape s)
	{
		path.append(s, false);
	}

	public void add(Collection<Point2D> points)
	{
		if (points.size() == 0)
			return;

		Iterator<Point2D> i = points.iterator();
		Point2D initial = i.next();

		path.moveTo((float) initial.getX(), (float) initial.getY());

		while (i.hasNext())
		{
			Point2D point = i.next();

			path.lineTo((float) point.getX(), (float) point.getY());
		}

		path.closePath();
	}

	public boolean contains(Point2D p)
	{
		return path.contains(p);
	}

	public boolean contains(Rectangle2D r)
	{
		return path.contains(r);
	}

	public boolean contains(double x, double y)
	{
		return path.contains(x, y);
	}

	public boolean contains(double x, double y, double w, double h)
	{
		return path.contains(x, y, w, h);
	}

	public Rectangle getBounds()
	{
		return path.getBounds();
	}

	public Rectangle2D getBounds2D()
	{
		return path.getBounds2D();
	}

	public PathIterator getPathIterator(AffineTransform at)
	{
		return path.getPathIterator(at);
	}

	public PathIterator getPathIterator(AffineTransform at, double flatness)
	{
		return path.getPathIterator(at, flatness);
	}

	public boolean intersects(Rectangle2D r)
	{
		return path.intersects(r);
	}

	public boolean intersects(double x, double y, double w, double h)
	{
		return path.intersects(x, y, w, h);
	}

	public Polygon getSubPolygon(int index)
	{
		return subPolygons.get(index);
	}

	public List<Polygon> getSubPolygons()
	{
		return subPolygons;
	}

	public ArrayList<Line> getLineSegments()
	{
		Point2D initialPoint = new Point2D.Double(), previousPoint = new Point2D.Double(), currentPoint = new Point2D.Double();
		double coords[] = new double[6];
		ArrayList<Line> polygonLines = new ArrayList<Line>();
		Stack<Point2D> startStack = new Stack<Point2D>();

		if (windDir == null)
			getWindingDirection();

		PathIterator pi = path.getPathIterator(null);

		pi.currentSegment(coords);
		initialPoint.setLocation(coords[0], coords[1]);
		previousPoint.setLocation(coords[0], coords[1]);

		while (!pi.isDone())
		{
			int coordType = pi.currentSegment(coords);

			if (coordType == PathIterator.SEG_LINETO)
			{
				//System.out.println("LINE: " + coords[0] + " " + coords[1]);
				currentPoint.setLocation(coords[0], coords[1]);

				if (!previousPoint.equals(currentPoint))
					polygonLines.add(new Line(previousPoint, currentPoint, windDir));

				previousPoint.setLocation(currentPoint);
			}
			else if (coordType == PathIterator.SEG_MOVETO)
			{
				//System.out.println("MOVE: " + coords[0] + " " + coords[1]);
				previousPoint.setLocation(coords[0], coords[1]);
				startStack.push((Point2D) previousPoint.clone());
			}
			else if (coordType == PathIterator.SEG_CLOSE)
			{
				initialPoint = startStack.pop();

				//System.out.println("LINE: " + initialPoint);
				if (!previousPoint.equals(initialPoint))
					polygonLines.add(new Line(previousPoint, initialPoint, windDir));
			}

			pi.next();
		}

		// Remove duplicate lines
		for (Iterator<Line> iter = polygonLines.iterator(); iter.hasNext();)
		{
			Line line2D = iter.next();

			if (line2D.getP1().equals(line2D.getP2()))
				iter.remove();
		}

		return polygonLines;
	}

	/**
	 *  Returns points for this polygon - not sub polygons
	 */
	public ArrayList<Point2D> getPoints()
	{
		double coords[] = new double[6];
		ArrayList<Point2D> pts = new ArrayList<Point2D>();

		PathIterator pi = path.getPathIterator(null);

		while (!pi.isDone())
		{
			int coordType = pi.currentSegment(coords);

			if (coordType == PathIterator.SEG_LINETO)
			{
				pts.add(new Point2D.Double(coords[0], coords[1]));
			}
			else if (coordType == PathIterator.SEG_MOVETO)
			{
				pts.add(new Point2D.Double(coords[0], coords[1]));
			}
			else if (coordType == PathIterator.SEG_CLOSE)
			{
				//Do nothing
			}

			pi.next();
		}

		return pts;
	}

	public double getArea()
	{
		ArrayList<Point2D> pts = getPoints();

		double area = 0.0;
		for (int i = 0; i < pts.size(); i++)
		{
			Point2D pt = pts.get(i);
			Point2D npt = pts.get((i + 1) % pts.size());

			area += pt.getX() * npt.getY() - npt.getX() * pt.getY();
		}

		return area / 2;
	}

	public WindingDirection getWindingDirection()
	{
		if (windDir == null)
		{
			if (getArea() < 0)
				windDir = WindingDirection.CCW;
			else
				windDir = WindingDirection.CW;
		}

		return windDir;
	}

	public void ensureWindingDirection(WindingDirection wDir)
	{
		//Nothing to do
		if (wDir.equals(getWindingDirection()))
			return;
		else
		{
			//Points are ordered in the wrong direction
			ArrayList<Point2D> pts = getPoints();
			path.reset();
			
			if (pts.size() == 0)
				return;

			ListIterator<Point2D> iter = pts.listIterator(pts.size());

			Point2D initialPoint = iter.previous();
			path.moveTo((float) initialPoint.getX(), (float) initialPoint.getY());

			while (iter.hasPrevious())
			{
				Point2D curPoint = iter.previous();
				path.lineTo((float) curPoint.getX(), (float) curPoint.getY());
			}

			path.closePath();
		}

		windDir = wDir;
	}

	private GeneralPath path;

	public Shape createTransformedShape(AffineTransform t)
	{
		return path.createTransformedShape(t);
	}

	public void addSubPolygon(Polygon s)
	{
		subPolygons.add(s);
	}

	private ArrayList<Polygon> subPolygons = new ArrayList<Polygon>(1);

	public WindingDirection windDir = null;

	public enum WindingDirection {
		CCW, CW
	};
}
